#include "Scanner.h"

// initialize the take and translationVector to all zeroes
int Scanner::table[128][STATE_SENTRY] = { 0 };
int Scanner::tokenVector[STATE_SENTRY] = { 0 };

/**
 * Constructor
 * @param filename of the file to scan
 */
Scanner::Scanner(std::string filename) {
	initTable();
	initTokenVector();
	
	currentLine = 1;
	scanningComment = endOfFile = false;
	this->filename = filename;

	sourceFile.open(filename.c_str());
	if(!sourceFile.is_open()) {
		// this should probably be done with exceptions
		perror("Error openning source file");
	}
}

/**
 * Destructor
 * Makes sure the source file is closed
 */
Scanner::~Scanner() {
	if(sourceFile.is_open()) {
		sourceFile.close();
	}
}

/**
 * Function used for skipping blanks
 * blanks are any ascii characters <= 32, also used to detect the end of
 * comment blocks
 */
void Scanner::skipBlanks() {
	while( scanningComment || currentChar<=32 ) {
		if( scanningComment && currentChar == '*') {
			if( nextChar() == '/') {
				scanningComment=false;
			}
		}
		nextChar();
		
		if( isEOF() ) return;
	}
}

/**
 * Function which retrieves the next character in the source file
 * It also keeps track of newlines and notifies characterObservers
 * @return the nect character in the source file
 */
char Scanner::nextChar() {
	sourceFile.get( currentChar );
	
	if( !isEOF() ) {
		notifyCharacterObservers( currentChar );
	}
	
	if( currentChar=='\n' ) { 
		++currentLine;
	}
	
	return currentChar;
}

/**
 * Function retrieves the next token in the source file
 * @return the next Token in the src file
 */
Token Scanner::getToken() {
	skipBlanks(); // make sure we are looking at a relevant character
	
	if( isEOF() ) {
		// if we have reached the end of file return an EOF token
		Token tok;
		tok.setLineNum(currentLine);
		if( scanningComment ) {
			/* if we were in the process of scanning a comment then give the
			 * appropriate error */
			tok.setType(TK_ERROR_UNTERMINATED_COMMENT);
			scanningComment=false;
		} else {
			tok.setType(TK_EOF);
		}
		notifyTokenObservers(tok); // tell the interested objects
		return tok;
	}
	
	std::string buffer;
	int currentState, nextState=INIT; // reset the table
	int lineNum = currentLine; // record the current line before we begin
	
	while(1) {
		currentState = nextState;
		nextState = table[currentChar][currentState];
		
		if(nextState != NO_TRANS) {
			buffer+=currentChar;
			nextChar();
		} else {
			break;
		}
	}
	
	// if started reading a comment...
	if(currentState == COMMENT) {
		scanningComment=true;
		return getToken(); // single recursion to get the next token after the comment
	}
	
	// get the TokenType from the translationVector
	Token token( (TokenType)tokenVector[currentState] );
	token.setLineNum( lineNum );
	token.setValue( buffer );
		
	notifyTokenObservers(token);
	return token;
}

/**
 * Check if we have reached the end of the source file
 * @return true if EOF is reached, false otherwise
 */
bool Scanner::isEOF() {
	if(!endOfFile && sourceFile.eof()) {
		endOfFile = true; 
		sourceFile.close();
	}
	return endOfFile;
}

/**
 * Get the name of the source file
 * @return a string with the filename of the src file
 */
std::string Scanner::getFilename() {
	return filename;
}

/**
 * Tell objects about this new character we just read
 */
void Scanner::notifyCharacterObservers(char c) {
	for(std::size_t i=0; i<characterObservers.size(); i++) {
		characterObservers[i]->updateCharacter(c);
	}
}

/**
 * Tell objects about this new Token we just extracted
 */
void Scanner::notifyTokenObservers(Token t) {
	for(std::size_t i=0; i<tokenObservers.size(); i++) {
		tokenObservers[i]->updateToken(t);
	}
}

/**
 * Add an observer to our list of character observers
 * @param a pointer to a CharacterObserver
 */
void Scanner::registerObserver(CharacterObserver* o) {
	characterObservers.push_back(o);
}

/**
 * Add an observer to our list of token observers
 * @param a pointer to a TokenObserver
 */
void Scanner::registerObserver(TokenObserver* o) {
	tokenObservers.push_back(o);
}

void Scanner::removeObserver(CharacterObserver* o) {
	for(std::vector<CharacterObserver*>::iterator i=characterObservers.begin(); 
		i!=characterObservers.end();
		++i)
	{
		if((*i) == o) {
			characterObservers.erase(i);
			return;
		}
	}
}

void Scanner::removeObserver(TokenObserver* o) {
	for(std::vector<TokenObserver*>::iterator i=tokenObservers.begin(); 
		i!=tokenObservers.end();
		++i)
	{
		if((*i) == o) {
			tokenObservers.erase(i);
			return;
		}
	}
}

/**
 * Initialize the translation vector which converts table states to token types
 */
void Scanner::initTokenVector() {
	/* this is possible because the states and token types are enumerated
	 * in the same order */
	for(int i=FIRST_TOKEN_STATE; i<PARTIAL_KEYWORDS; ++i) {
		tokenVector[i]=(i-FIRST_TOKEN_STATE)+Token::FIRST_TOKEN_STATE;
	}
	
	/* partial keywords can be used as identifiers */
	for(int i=PARTIAL_KEYWORDS; i<STATE_SENTRY; ++i) {
		tokenVector[i]=TK_IDENTIFIER;
	}
}

/**
 * Initialize the scanner's transition table
 */
void Scanner::initTable() {
	int i;
	for(i=32; i<127; ++i) {
		table[i][QUOTE]=QT_AND_CHAR;
	}
	
	table['!'][INIT]=EXCLAMATION;
	table['"'][INIT]=DOUBLE_QUOTE;
	table['#'][INIT]=NUMBER_SIGN;
	table['$'][INIT]=DOLLAR;
	table['%'][INIT]=PERCENT;
	
	table['&'][INIT]=AMPERSAND;
	table['&'][AMPERSAND]=LOGIC_AND;
	
	table['\''][INIT]=QUOTE;
	table['\''][QT_AND_CHAR]=CHAR_CONST;
	
	table['('][INIT]=LEFT_PARENTHESES;
	table[')'][INIT]=RIGHT_PARENTHESES;
	
	table['*'][INIT]=ASTERISK;
	table['*'][SLASH]=COMMENT;
	
	table['+'][INIT]=PLUS;
	table[','][INIT]=COMMA;
	table['-'][INIT]=MINUS;
	table['.'][INIT]=DOT;
	
	table['/'][INIT]=SLASH;
	
	for(i='0'; i<='9'; ++i) {
		table[i][INIT]=NUM;
		table[i][IDENTIFIER]=IDENTIFIER;
		table[i][NUM]=NUM;
		for(int j=KEYWORDS; j<STATE_SENTRY; ++j) {
			table[i][j]=IDENTIFIER;
		}
	}
	
	table[':'][INIT]=COLON;
	table[';'][INIT]=SEMICOLON;
	table['<'][INIT]=LT;
	
	table['='][INIT]=ASSIGNMENT;
	table['='][EXCLAMATION]=NOT_EQ;
	table['='][LT]=LTE;
	table['='][GT]=GTE;
	table['='][ASSIGNMENT]=EQUAL;

	table['>'][INIT]=GT;
	table['?'][INIT]=QUESTION;
	table['@'][INIT]=AT;
	
	for(i='A'; i<='Z'; ++i) {
		table[i][INIT]=IDENTIFIER;
		table[i][IDENTIFIER]=IDENTIFIER;
		for(int j=KEYWORDS; j<STATE_SENTRY; ++j) {
			table[i][j]=IDENTIFIER;
		}
	}

	table['['][INIT]=LEFT_BRACKET;
	table['\\'][INIT]=BACKSLASH;
	table[']'][INIT]=RIGHT_BRACKET;
	table['^'][INIT]=CARET;
	
	table['_'][INIT]=IDENTIFIER;
	table['_'][IDENTIFIER]=IDENTIFIER;
	for(int j=KEYWORDS; j<STATE_SENTRY; ++j) {
		table['_'][j]=IDENTIFIER;
	}
	
	table['`'][INIT]=GRAVE_ACCENT;
	
	for(i='a'; i<='z'; ++i) {
		table[i][INIT]=IDENTIFIER;
		table[i][IDENTIFIER]=IDENTIFIER;
		for(int j=KEYWORDS; j<STATE_SENTRY; ++j) {
			table[i][j]=IDENTIFIER;
		}
	}
	
	table['{'][INIT]=LEFT_BRACE;
	
	table['|'][INIT]=VERT_BAR;
	table['|'][VERT_BAR]=LOGIC_OR;
	
	table['}'][INIT]=RIGHT_BRACE;
	table['~'][INIT]=TILDE;
	
	/* KEYWORDS */
	table['c'][INIT]=KW_C;
	table['h'][KW_C]=KW_CH;
	table['a'][KW_CH]=KW_CHA;
	table['r'][KW_CHA]=KW_CHAR;
	
	table['e'][INIT]=KW_E;
	table['l'][KW_E]=KW_EL;
	table['s'][KW_EL]=KW_ELS;
	table['e'][KW_ELS]=KW_ELSE;
	
	table['i'][INIT]=KW_I;
	table['f'][KW_I]=KW_IF;
	table['n'][KW_I]=KW_IN;
	table['t'][KW_IN]=KW_INT;
	
	table['m'][INIT]=KW_M;
	table['a'][KW_M]=KW_MA;
	table['i'][KW_MA]=KW_MAI;
	table['n'][KW_MAI]=KW_MAIN;
	
	table['p'][INIT]=KW_P;
	table['r'][KW_P]=KW_PR;
	table['i'][KW_PR]=KW_PRI;
	table['n'][KW_PRI]=KW_PRIN;
	table['t'][KW_PRIN]=KW_PRINT;
	table['f'][KW_PRINT]=KW_PRINTF;
	
	table['r'][INIT]=KW_R;
	table['e'][KW_R]=KW_RE;
	table['t'][KW_RE]=KW_RET;
	table['u'][KW_RET]=KW_RETU;
	table['r'][KW_RETU]=KW_RETUR;
	table['n'][KW_RETUR]=KW_RETURN;
	
	table['s'][INIT]=KW_S;
	table['c'][KW_S]=KW_SC;
	table['a'][KW_SC]=KW_SCA;
	table['n'][KW_SCA]=KW_SCAN;
	table['f'][KW_SCAN]=KW_SCANF;
	
	table['v'][INIT]=KW_V;
	table['o'][KW_V]=KW_VO;
	table['i'][KW_VO]=KW_VOI;
	table['d'][KW_VOI]=KW_VOID;
	
	table['w'][INIT]=KW_W;
	table['h'][KW_W]=KW_WH;
	table['i'][KW_WH]=KW_WHI;
	table['l'][KW_WHI]=KW_WHIL;
	table['e'][KW_WHIL]=KW_WHILE;
}
